package com.motu.vertx.module.utility.model;

import com.motu.vertx.module.utility.base.BaseServerParam;
import com.motu.vertx.module.utility.base.ServerConfig;
import com.motu.vertx.module.utility.redis.RedisTools;
import com.motu.vertx.module.utility.toolset.TimeUtils;
import com.motu.vertx.module.utility.toolset.Tool;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
//import io.vertx.ext.sync.Sync;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BaseModel {
    public final static Logger logger = LoggerFactory.getLogger(BaseModel.class);

    public final static int INSERT = 1;// 新增
    public final static int UPDATE = 2;// 修改
    public final static int DELETE = 3;// 删除

    public final static int UPDATE_DB_TYPE_QUEUE = 0;// 队列入库
    public final static int UPDATE_DB_TYPE_NOW = 1;// 直接入库
    public final static int UPDATE_DB_TYPE_NOT = 2;// 不入库

    public final static String UPDATE_DB_TIME = "updateDBTime";// 更新数据库时间key
    public final static String REDIS_EXPIRE_TIME = "redisExpireTime";// redis过期时间key

    protected JsonObject updateObj;
    protected long updateDBTime;// 最后入库时间
    protected long redisExpireTime;// redis过期时间（存到redis中是时间戳，序列化成该字段的值）

    /**
     * 判断某个字段是否有修改
     */
    protected boolean isFieldUpdate(String field) {
        return updateObj != null && updateObj.containsKey(field);
    }

    public long getUpdateDBTime() {
        return updateDBTime;
    }

    public void setUpdateDBTime(long updateDBTime) {
        this.updateDBTime = updateDBTime;
        if (updateObj != null) {
            updateObj.put(UPDATE_DB_TIME, updateDBTime);
        }
    }

    public static JsonObject removeTempkey(JsonObject updateObj) {
        if (updateObj != null) {
            updateObj.remove(UPDATE_DB_TIME);
            updateObj.remove(REDIS_EXPIRE_TIME);
        }
        return updateObj;
    }

    public long getRedisExpireTime() {
        return redisExpireTime;
    }

    public void setRedisExpireTime(int redisExpireTime) {
        this.redisExpireTime = redisExpireTime;
        if (updateObj != null) {
            updateObj.put(REDIS_EXPIRE_TIME, redisExpireTime + TimeUtils.getCurTime());
        }
    }

    protected void updateRedisHashField(String key, String field, Object value) {
        if (updateObj == null) {
            updateObj = new JsonObject();
        }
        updateObj.put(field, value);
    }

    /**
     * 批量设置更新字段（特殊场景下使用）
     *
     * @param obj 需要保证key和Value与数据对象Model的内容一致，否则会导致数据库写入异常，建议只用于传入getAllObj的数据
     */
    public void updateManyRedisHashField(JsonObject obj) {
        if (obj != null) {
            if (this.updateObj == null) {
                this.updateObj = new JsonObject();
            }
            this.updateObj.mergeIn(obj);
        }
    }

    protected JsonObject insert(String key, long id, int expireTime, int updateDbType, String listKeyField, String listKey, String listItemKey) {
        return insert(key, id, expireTime, updateDbType, listKeyField, listKey, listItemKey, false);
    }

    protected JsonObject insert(String key, long id, int expireTime, int updateDbType, String listKeyField, String listKey, String listItemKey, boolean orderList) {

        // 更新的对象为空则不用操作
        if (updateObj == null || updateObj.isEmpty()) {
            logger.error("insert error updateObj isEmpty key = " + key);
            return new JsonObject();
        }

        /**
         * 单条数据
         */
        if (Tool.isEmpty(listKey)) {
            // 数据需要入库时
            if (updateDbType != UPDATE_DB_TYPE_NOT) {
                // 设置最后入库时间（延迟入库时比较）
                setUpdateDBTime(TimeUtils.getCurTime());

                // 标记数据已经初始化
                setEntityInit(key);
            }

            // 实体对象标记过期时间（下次设置过期时间比较）
            saveEntityExpireTime(expireTime);

            // 保存实体对象到redis中
            Future<Boolean> future = RedisTools.setHashJsonObject(key, updateObj);
            Boolean result = Future.await(future);

            // 设置实体对象过期时间（新增时必定会设置过期时间）
            putRedisExpireTime(key, expireTime);
        }

        /**
         * 列表数据
         */
        else {
            // 列表数据是否存在
            boolean exist = false;

            // 数据需要入库时
            if (updateDbType != UPDATE_DB_TYPE_NOT) {
                // 列表数据如果过期从数据库加载
                exist = EntityManager.listExpireLoadFromDB(getClass(), listKeyField, listKey);

                // 设置最后入库时间（延迟入库时比较）
                setUpdateDBTime(TimeUtils.getCurTime());

                // 标记列表数据已经初始化
                if (!exist) {
                    setEntityInit(listKey);
                }
            }

            // 实体对象标记过期时间（列表也用每个对象里的过期时间做比较，就不另外存储过期时间字段，不然要多交互一次）
            saveEntityExpireTime(expireTime);

            // 保存实体对象到redis中
            JsonObject entityList = new JsonObject();
            entityList.put(listItemKey, updateObj);
            Future<Boolean> future = RedisTools.setHashJsonObject(listKey, entityList);
            Boolean result = Future.await(future);

            // 额外的有序列表
            if (orderList){
                String orderListKey = EntityManager.getOrderListKey(listKey);
                Promise<Boolean> promise1 = Promise.promise();
                RedisTools.pushToList(orderListKey, listItemKey, promise1);
                Future.await(promise1.future());

                // 设置有序列表过期时间
                if (!exist) {
                    putRedisExpireTime(orderListKey, expireTime);
                }
            }

            // 设置列表对象过期时间
            if (!exist) {
                putRedisExpireTime(listKey, expireTime);
            }
        }

        // 更新到数据库
        return updateDb(key, id, INSERT, updateDbType, null, listKeyField);
    }

    /**
     * 修改数据
     *
     * @param key             rediskey
     * @param id              数据dbid
     * @param expireTime      过期时间
     * @param updateDbType    更新数据库类型
     * @param delayToDbUserId 延迟入库用户ID（没有配置的直接入库，有配置的延迟入库并添加到用户队列中）
     * @param allObj          所有字段
     */
    protected JsonObject update(String key, long id, int expireTime, int updateDbType, long delayToDbUserId, JsonObject allObj, String listKeyField, String listKey, String listItemKey) {
        if (updateObj == null || updateObj.isEmpty()) {
            return new JsonObject();
        }

        // 本次更新是否需要入库
        boolean updateDb;

        // 判断是否延迟入库
        long curTime = TimeUtils.getCurTime();
        if (updateDbType == UPDATE_DB_TYPE_NOT) {
            updateDb = false;
        } else {
            if (delayToDbUserId == 0 || updateDbType == UPDATE_DB_TYPE_NOW) {
                // 非玩家数据则直接入库
                updateDb = true;
            } else {

                // 玩家数据延迟入库
                if (curTime - getUpdateDBTime() > BaseServerParam.UPDATE_DB_DELAY_TIME) {
                    // 超过延迟更新时间，则立即入库
                    setUpdateDBTime(curTime);
                    updateDb = true;

                    // 从用户带更新列表中移除
                    RedisTools.delToSet(EntityManager.getUserUpdateDbListKey(delayToDbUserId), getDelayUpdateDBKey(key, listKeyField, listKey, listItemKey));
                } else {
                    // 不能立即更新数据库，添加到用户待更新列表中
                    setUpdateDBTime(getUpdateDBTime());
                    updateDb = false;
                    RedisTools.addToSet(EntityManager.getUserUpdateDbListKey(delayToDbUserId), getDelayUpdateDBKey(key, listKeyField, listKey, listItemKey));
                }
            }
        }

        // 设置过期时间
        if (getRedisExpireTime() - curTime < BaseServerParam.REDIS_EXPIRE_UPDATE_TIME) {
            // 过期时间每个对象上都有标记
            saveEntityExpireTime(expireTime);

            // 列表数据是需要给整个列表对象设置过期时间
            putRedisExpireTime(Tool.isEmpty(listKey) ? key : listKey, expireTime);
        }

        // 更新redis对象
        if (Tool.isEmpty(listKey)) {
            // 更新单条数据
            Future<Boolean> future = RedisTools.setHashJsonObject(key, updateObj);
            Boolean setResult = Future.await(future);
        } else {
            // 更新列表数据（列表需要更新全部字段）
            JsonObject entityList = new JsonObject();
            if (updateObj.containsKey(UPDATE_DB_TIME)){
                allObj.put(UPDATE_DB_TIME, updateObj.getInteger(UPDATE_DB_TIME));
            }
            if (updateObj.containsKey(REDIS_EXPIRE_TIME)){
                allObj.put(REDIS_EXPIRE_TIME, updateObj.getInteger(REDIS_EXPIRE_TIME));
            }
            entityList.put(listItemKey, allObj);
            Future<Boolean> future = RedisTools.setHashJsonObject(listKey, entityList);
            Boolean result = Future.await(future);
        }

        // 更新数据库
        if (updateDb) {
            return updateDb(key, id, UPDATE, updateDbType, allObj, listKeyField);
        } else {
            // 每次更新完把updateObj置为空，避免同个对象多次更新
            JsonObject temp = updateObj;
//            updateObj = null;// 这里不能置为空，不然下发给客户端的时候不知道修改了哪些字段
            return temp;
        }
    }

    protected void delete(String key, long id, int updateDbType, long delayToDbUserId, String listKeyField, String listKey, String listItemKey) {
        delete(key, id, updateDbType, delayToDbUserId, listKeyField, listKey, listItemKey, false);
    }

    protected void delete(String key, long id, int updateDbType, long delayToDbUserId, String listKeyField, String listKey, String listItemKey, boolean orderList) {

        if (Tool.isEmpty(listKeyField)) {
            // 删除单条数据
            Future<Boolean> future = RedisTools.del(key);
            Boolean result = Future.await(future);

            // 删除初始化标记
            if (updateDbType != UPDATE_DB_TYPE_NOT) {
                delEntityInit(key);
            }
        } else {
            // 删除列表数据
            Future<Boolean> hdel = RedisTools.hdel(listKey, listItemKey);
            Boolean result = Future.await(hdel);

            // 删除初始化标记
            if (updateDbType != UPDATE_DB_TYPE_NOT) {
                // 当hash所有字段都被删除时，整个列表对象也不存在了，这时候需要删除列表初始化标记
                Future<Boolean> booleanFuture = RedisTools.exists(listKey);
                Boolean exists = Future.await(booleanFuture);
                if (!exists) {
                    delEntityInit(listKey);
                }
            }

            // 额外的有序列表
            if (orderList){
                String orderListKey = EntityManager.getOrderListKey(listKey);
                Future<Long> delToList = RedisTools.delToList(orderListKey, listItemKey);
                Long result1 = Future.await(delToList);
            }
        }

        // 从用户待更新数据库列表中移除
        if (delayToDbUserId > 0) {
            RedisTools.delToSet(EntityManager.getUserUpdateDbListKey(delayToDbUserId), getDelayUpdateDBKey(key, listKeyField, listKey, listItemKey));
        }

        // 更新数据库
        updateDb(key, id, DELETE, updateDbType, null, listKeyField);
    }

    // 标记数据已经初始化（数据过期如果有初始化标记才需要从数据库加载）
    protected void setEntityInit(String key) {
        RedisTools.set(EntityManager.getEntityInitKey(key), "1");
    }

    // 删除初始化标记
    protected void delEntityInit(String key) {
        RedisTools.del(EntityManager.getEntityInitKey(key));
    }

    // 更新到数据库
    protected JsonObject updateDb(String key, long id, int type, String listKeyField) {
        return updateDb(key, id, type, UPDATE_DB_TYPE_QUEUE, null, listKeyField);
    }

    // 更新到数据库
    protected JsonObject updateDb(String key, long id, int type, int updateDbType, JsonObject allObj, String listKeyField) {

        // 获取对象里记录的更新数据库时间和过期时间（因为后面会删除，所以需要提前获取）
        int updateDBTime = updateObj != null && updateObj.containsKey(UPDATE_DB_TIME) ? updateObj.getInteger(UPDATE_DB_TIME) : 0;
        int redisExpireTime = updateObj != null && updateObj.containsKey(REDIS_EXPIRE_TIME) ? updateObj.getInteger(REDIS_EXPIRE_TIME) : 0;

        if (updateDbType != UPDATE_DB_TYPE_NOT) {
            String entityKey = key.split("\\.")[0];// 新规范类名，旧规范表名
            String tableName = EntityManager.isNewCode() ? Tool.upperCaseToUnderline(entityKey) : entityKey;
            JsonObject updateDb = new JsonObject();
            updateDb.put("status", type);// 0需要判断是否存在数据库，1新增，2修改，3删除
            updateDb.put("table", tableName);
            updateDb.put("id", id);
            updateDb.put("key", key);
            if (listKeyField.contains(".")) {// listkey有多个字段
                updateDb.put("listkey", listKeyField);
            }
            if (updateObj != null) {
                if (allObj != null) {
                    // 更新所有字段（update时更新所有字段）
                    removeTempkey(allObj);// 删除临时字段，不然会把临时字段更新到数据库中
                    updateDb.put("update", allObj);
                } else {
                    // 更新修改字段（insert时更新修改字段）
                    removeTempkey(updateObj);// 删除临时字段，不然会把临时字段更新到数据库中
                    updateDb.put("update", updateObj);
                }
            }

            if (updateDbType == UPDATE_DB_TYPE_NOW) {
                // 立刻更新到数据库
                DbManager.updateDbNow(updateDb);
            }
//			else if (ServerConfig.isReviewServer()) {
//                // 评审服用redis队列写入数据库
//                RedisTools.pushToList(EntityManager.getUpdateDbKey(), updateDb.toString());
//            }
			else {
                // 写入数据库队列
                DbManager.pushDBQueue(tableName, key, updateDb);
            }
        }

        // 每次更新完把updateObj置为空，避免同个对象多次更新
        JsonObject temp = updateObj;
//        updateObj = null;// 这里不能置为空，不然下发给客户端的时候不知道修改了哪些字段

        // 返回的JsonObject需要保留临时字段，这样调用getUniqueEntity方法如果有insert，之后的update操作才会延迟更新，因为insert之后，是把返回的json对象直接转成entity对象
        if (updateDBTime > 0) {
            temp.put(UPDATE_DB_TIME, updateDBTime);
        }
        if (redisExpireTime > 0) {
            temp.put(REDIS_EXPIRE_TIME, redisExpireTime);
        }
        return temp;
    }


    //通过队列删除某个id的数据
    protected void delToDBQueue(String key, long id, int updateDbType) {
        if (updateDbType == UPDATE_DB_TYPE_NOT) {
            return;
        }
        // 加入DB队列
        String entityKey = key.split("\\.")[0];// 新规范类名，旧规范表名
        String tableName = EntityManager.isNewCode() ? Tool.upperCaseToUnderline(entityKey) : entityKey;
        JsonObject updateDb = new JsonObject();
        updateDb.put("status", DELETE);// 0需要判断是否存在数据库，1新增，2修改，3删除
        updateDb.put("table", tableName);
        updateDb.put("id", id);
        updateDb.put("key", key);
//        if (ServerConfig.isReviewServer()) {// 评审服用redis队列写入数据库
//            RedisTools.pushToList(EntityManager.getUpdateDbKey(), updateDb.toString());
//        } else {
            DbManager.pushDBQueue(tableName, key, updateDb);
//        }
    }

    protected long incrTableId(String key) {
        long initId = BaseServerParam.TABLE_INIT_ID;
        Future<Boolean> hsetnx = RedisTools.hsetnx(RedisTools.getTableidKey(), key, String.valueOf(initId));
        boolean init = Future.await(hsetnx);
        if (init) {
            logger.info("### table = " + key + " init id = " + initId);
            return initId;
        } else {
            Future<Long> incrTableId = RedisTools.incrTableId(key);
            long r = Future.await(incrTableId);
            return r;
        }
    }

    /**
     * 设置redis实体类过期时间
     *
     * @param key        redis key
     * @param expireTime 过期时间（秒）
     */
    private void putRedisExpireTime(String key, int expireTime) {
        if (BaseServerParam.REDIS_EXPIRE_TIME == 0) {
            return;
        }
        // 设置过期时间
        if (expireTime == 0) {
            expireTime = BaseServerParam.REDIS_EXPIRE_TIME;
        }
        if (expireTime > 0) {
            RedisTools.expire(key, expireTime);
        }
    }

    /**
     * redis实体类对象保存过期时间
     *
     * @param expireTime 过期时间（秒）
     */
    private void saveEntityExpireTime(int expireTime) {
        if (BaseServerParam.REDIS_EXPIRE_TIME == 0) {
            return;
        }
        if (expireTime == 0) {
            expireTime = BaseServerParam.REDIS_EXPIRE_TIME;
        }
        if (expireTime > 0) {
            setRedisExpireTime(expireTime);
        }
    }

    /**
     * 获取延迟更新数据库的key
     *
     * @param key          数据的key
     * @param listKeyField 列表数据的关键字段名
     * @param listKey      列表数据key
     * @param listItemKey  列表数据itemkey
     */
    private String getDelayUpdateDBKey(String key, String listKeyField, String listKey, String listItemKey) {
        if (Tool.isEmpty(listKeyField)) {
            return key;
        } else {
            return key + ";" +listKeyField + ";" + listKey + ";" + listItemKey;
        }
    }

    /**
     * 如果超过上限则会删除最早的那条
     */
    public void delFirst(String key, String listKey, int UPDATE_DB_TYPE, int max) {
        if (max <= 0) {
            return;
        }
        String orderListKey = EntityManager.getOrderListKey(listKey);
        Future<Long> hashLen = RedisTools.getHashLen(orderListKey);
        long count = Future.await(hashLen);
        if (count >= max) {
            Future<String> lpop = RedisTools.lpop(orderListKey);
            String delRecord = Future.await(lpop);// 删除列表数据
            long id = Long.parseLong(delRecord);// dbid
            RedisTools.hdel(listKey, delRecord, h->{});// 删除hash item对象
            delToDBQueue(key, id, UPDATE_DB_TYPE);
        }
    }

}
